package ln.gather.jsr.jndi;

import ln.gather.jsr.jndi.function.ThrowableAction;
import ln.gather.jsr.jndi.repository.JndiComponentRepository;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.servlet.ServletContext;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 基于 JNDI 实现的组件上下文
 */
public class JndiComponentContext implements ComponentContext {

    public static final String CONTEXT_NAME = JndiComponentContext.class.getName();

    private static ServletContext servletContext;

    /**
     * @PreDestroy 方法缓存，key 为标注方法，value为方法所属对象
     */
    private Map<Method, Object> preDestroyMethodCache = new LinkedHashMap<>();

    /**
     * 组件仓库
     */
    private JndiComponentRepository repository = new JndiComponentRepository();

    /**
     * 获取 组件上下文实例
     */
    public static JndiComponentContext getInstance() {
        return (JndiComponentContext) servletContext.getAttribute(CONTEXT_NAME);
    }

    /**
     * 根据外部的 servletContext 来进行初始化
     */
    public void init(ServletContext servletContext) throws RuntimeException {
        JndiComponentContext.servletContext = servletContext;
        servletContext.setAttribute(CONTEXT_NAME, this);
        this.initialize();
    }


    /**
     * <pre>
     * 初始化
     * 步骤：
     * 1. 组件仓库初始化
     *  1.1 初始化类加载器
     *  1.2 初始化环境上下文
     * 2. 实例化组件
     *  2.1 通过 JNDI 查找所有的组件名称
     *  2.2 循环实例化组件
     * 3. 初始化组件
     *  3.1 注入阶段 - {@link Resource}
     *  3.2 初始阶段 - {@link PostConstruct}
     *  3.3 销毁阶段 - {@link PreDestroy}
     * 4. 挂在销毁钩子
     *
     * </pre>
     */
    @Override
    public void initialize() {
        // 1. 组件仓库初始化
        repository.initialize();
        // 2. 实例化组件
        instantiateComponents();
        // 3. 初始化组件
        initializeComponents();
        // 4. 挂在销毁钩子
        registerShutdownHook();
    }

    /**
     * 2. 实例化组件
     * 2.1 通过 JNDI 查找所有的组件名称
     * 2.2 循环实例化组件
     */
    protected void instantiateComponents() {
        // 遍历所有组件的名称
        Set<String> componentNames = repository.listComponentNames();
        //通过依赖查找，实例化对象（ Tomcat BeanFactory setter 方法的执行，仅支持简单类型）
        componentNames.forEach(repository::getComponent);
    }

    /**
     * 3. 初始化组件
     * 3.1 注入阶段 - {@link Resource}
     * 3.2 初始阶段 - {@link PostConstruct}
     * 3.3 销毁阶段 - {@link PreDestroy}
     */
    protected void initializeComponents() {
        repository.getComponents().values().forEach(this::initializeComponent);
    }

    /**
     * 3. 初始化组件
     * 3.1 注入阶段 - {@link Resource}
     * 3.2 初始阶段 - {@link PostConstruct}
     * 3.3 销毁阶段 - {@link PreDestroy}
     */
    protected void initializeComponent(Object component) {
        Class<?> componentClass = component.getClass();
        // 3.1 注入阶段 - {@link Resource}
        injectComponent(component, componentClass);
        // 保存候选方法用户初始化与销毁
        List<Method> candidateMethods = findCandidateMethods(componentClass);
        // 3.2 初始阶段 - {@link PostConstruct}
        processPostConstruct(component, candidateMethods);
        // 3.3 销毁阶段 - {@link PreDestroy}：将销毁方法保存起来
        processPreDestroyMetadata(component, candidateMethods);
    }

    /**
     * 3.3 销毁阶段 - {@link PreDestroy}：将销毁方法保存起来
     */
    private void processPreDestroyMetadata(Object component, List<Method> candidateMethods) {
        candidateMethods.stream()
                .filter(method -> method.isAnnotationPresent(PreDestroy.class))
                .forEach(method -> {
                    preDestroyMethodCache.put(method, component);
                });
    }

    /**
     * 3.2 初始阶段 - {@link PostConstruct}
     */
    private void processPostConstruct(Object component, List<Method> candidateMethods) {
        candidateMethods.stream()
                .filter(method -> method.isAnnotationPresent(PostConstruct.class))
                .forEach(method -> {
                    ThrowableAction.execute(() -> method.invoke(component));
                });
    }

    /**
     * 查找候选方法
     * 保留非静态 && public && 无参数 的方法
     */
    private List<Method> findCandidateMethods(Class<?> componentClass) {
        return Stream.of(componentClass.getMethods()) // public 方法
                .filter(method -> !Modifier.isStatic(method.getModifiers()) && method.getParameterCount() == 0)
                .collect(Collectors.toList());
    }

    /**
     * 3.1 注入阶段 - {@link Resource}
     */
    protected void injectComponent(Object component, Class<?> componentClass) {
        // 遍历该组件中所有的字段
        Stream.of(componentClass.getDeclaredFields())
                // 只保留非静态的且被@Resource标注的字段
                .filter(field -> !Modifier.isStatic(field.getModifiers()) && field.isAnnotationPresent(Resource.class))
                .forEach(field -> {
                    // 获取 @Resource 注解
                    Resource resource = field.getAnnotation(Resource.class);
                    // 获取 资源名称(组件名称)
                    String resourceName = resource.name();
                    // 根据组件名称寻找组件实例
                    Object injectedObject = lookupComponent(resourceName);
                    // 强制可访问
                    field.setAccessible(true);
                    try {
                        // 进行注入
                        field.set(component, injectedObject);
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    }
                });
    }


    /**
     * 4. 挂在销毁钩子
     */
    private void registerShutdownHook() {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> processPreDestroy()));
    }

    /**
     * 预销毁方法
     */
    private void processPreDestroy() {
        preDestroyMethodCache.keySet().forEach(preDestroyMethod -> {
            // 先移除方法，防止重复调用
            Object component = preDestroyMethodCache.remove(preDestroyMethod);
            // 执行方法
            ThrowableAction.execute(() -> preDestroyMethod.invoke(component));
        });
    }

    /**
     * 销毁
     */
    @Override
    public void destroy() {

    }

    /**
     * 根据组件名称寻找组件实例
     */
    public <C> C lookupComponent(String name) {
        return repository.loadComponent(name);
    }
}
